﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace BaseDomainNamespace.Viewpoints
{
    /// <summary>
    /// 順序を表します。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 順序は、視点による並び替えを高度にサポートします。
    /// </para>
    /// <para>
    /// 並び替えを行うには、
    /// <see cref="Order&lt;TEntity&gt;.ApplyTo(IEnumerable&lt;TEntity&gt;)"/> メソッド、
    /// または <see cref="Order&lt;TEntity&gt;.ApplyTo(IQueryable&lt;TEntity&gt;)"/> メソッドを使用します。
    /// これらのメソッドを複数の順序で構成される順序で使用した場合、先頭の視点から順番に並べ替え基準として適用していきます。
    /// 複数の視点を組み合わせるには、基となる順序に対して 
    /// <see cref="Order&lt;TEntity&gt;.Then&lt;TFollowingValue&gt;(SimpleOrder&lt;TEntity, TFollowingValue&gt;)"/> メソッドを呼び出し後続の順序を引数として渡します。
    /// <see cref="Order&lt;TEntity&gt;.Then&lt;TFollowingValue&gt;(SimpleOrder&lt;TEntity, TFollowingValue&gt;)"/> メソッドによって生成された新しい順序に対して更に <see cref="Order&lt;TEntity&gt;.Then&lt;TFollowingValue&gt;(SimpleOrder&lt;TEntity, TFollowingValue&gt;)"/> メソッドを呼び出すことで任意の数の順序を組み合わせることができます。
    /// </para>
    /// </remarks>
    /// <example>
    /// 視点を組み合わせて順序を生成する方法を次のコード例に示します。
    /// <code>
    ///     var order = new ToDoStateViewpoint().Asc()
    ///                 .Then(new ToDoRegisteredDateTimeViewpoint().Asc());
    /// </code>
    /// </example>
    /// <typeparam name="TEntity">エンティティの型。</typeparam>
    /// <seealso cref="Viewpoint&lt;TEntity, TValue&gt;"/>
    /// <seealso cref="SimpleOrder&lt;TEntity, TValue&gt;"/>
    /// <seealso cref="ComplexOrder&lt;TEntity&gt;"/>
    [Serializable]
    public abstract class Order<TEntity>
        where TEntity : class
    {
        /// <summary>
        /// <see cref="Order&lt;TEntity&gt;"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        internal Order()
        {
        }


        /// <summary>
        /// 指定したシーケンスの要素を並べ替えます。
        /// </summary>
        /// <param name="target">シーケンス。</param>
        /// <returns>順序に従って要素が並び替えられているシーケンス。</returns>
        /// <exception cref="System.ArgumentNullException">target が null です。</exception>
        public IEnumerable<TEntity> ApplyTo(IEnumerable<TEntity> target)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }
            return OrderFirstly(target.AsQueryable()).AsEnumerable();
        }

        /// <summary>
        /// 指定したシーケンスの要素を並べ替えます。
        /// </summary>
        /// <param name="target">シーケンス。</param>
        /// <returns>順序に従って要素が並び替えられているシーケンス。</returns>
        /// <exception cref="System.ArgumentNullException">target が null です。</exception>
        public IQueryable<TEntity> ApplyTo(IQueryable<TEntity> target)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }
            return OrderFirstly(target);
        }


        /// <summary>
        /// 指定したシーケンスの要素を一次的に並び替えます。
        /// </summary>
        /// <param name="target">シーケンス。</param>
        /// <returns>順序に従って要素が並び替えられているシーケンス。</returns>
        internal abstract IOrderedQueryable<TEntity> OrderFirstly(IQueryable<TEntity> target);

        /// <summary>
        /// 既に並び替えられているシーケンスの要素を副次的に並び替えます。
        /// </summary>
        /// <param name="target">シーケンス。</param>
        /// <returns>順序に従って要素が並び替えられているシーケンス。</returns>
        internal abstract IOrderedQueryable<TEntity> OrderFurthermore(IOrderedQueryable<TEntity> target);


        /// <summary>
        /// 指定した順序を後続の順序として組み合わせた、新しい順序を生成します。
        /// </summary>
        /// <param name="following">後続の順序。</param>
        /// <returns>指定した順序を後続の順序として組み合わせた、新しい順序。</returns>
        /// <exception cref="System.ArgumentNullException">following が null です。</exception>
        public abstract ComplexOrder<TEntity> Then<TFollowingValue>(SimpleOrder<TEntity, TFollowingValue> following);
    }
}
